package com.meilai.project.util;

import cn.hutool.core.collection.CollUtil;

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
import java.util.*;

/**
 * @author xchen
 * @since 2022-03-15
 */
public class DateRangeUtil {

	/**
	 * 周六-周五为一个周期，计算包含指定开始结束日期的日期区间列表
	 */
	public static List<DateRange> calcWeekRange(LocalDate beginDate, LocalDate endDate) {
		List<DateRange> result = new ArrayList<>();
		if (beginDate.isAfter(endDate)) {
			return result;
		}
		LocalDate saturday = beginDate.with(TemporalAdjusters.previousOrSame(DayOfWeek.SATURDAY));
		LocalDate friday = endDate.with(TemporalAdjusters.nextOrSame(DayOfWeek.FRIDAY));
		while (saturday.isBefore(friday)) {
			DateRange item = new DateRange();
			item.setBeginDate(saturday);
			item.setEndDate(saturday.plusDays(6));
			result.add(item);
			saturday = saturday.plusWeeks(1);
		}
		return result;
	}

	public static Map<DateRangeType, Integer> calc(LocalDate beginDate, LocalDate endDate, List<DateRangeType> types) {
		return calc(beginDate, endDate, types.toArray(new DateRangeType[0]));
	}


	/**
	 * 计算给定日期区间内，各个指定时间区间类型的时间段对应的数量
	 * 比如：2022-02-10~2022-04-15 有：
	 * {
	 * "NATURE_WEEK": 3, --包含3个自然后
	 * "NATURE_MONTH": 1, -- 一个自然月
	 * "NATURE_DAY": 13 -- 13个自然天
	 * }
	 *
	 * @param beginDate 开始日期
	 * @param endDate   结束日期
	 * @param types     要计算哪几种日期类型
	 * @return 对应日期类型的天数
	 */
	public static Map<DateRangeType, Integer> calc(LocalDate beginDate, LocalDate endDate, DateRangeType... types) {
		Set<DateRangeType> typeSet = CollUtil.newHashSet(types);
		DateRange dateRange = new DateRange(beginDate, endDate);
		Map<DateRangeType, Integer> result = new HashMap<>();
		List<DateRange> natureMonthList = new ArrayList<>();
		List<DateRange> remainingMonthList = new ArrayList<>();
		List<DateRange> weekList = new ArrayList<>();
		List<DateRange> remainingWeekList = new ArrayList<>();
		//设置默认值
		result.put(DateRangeType.NATURE_DAY, 0);
		result.put(DateRangeType.NATURE_WEEK, 0);
		result.put(DateRangeType.NATURE_MONTH, 0);

		if (typeSet.contains(DateRangeType.NATURE_MONTH)) {
			//List<DateRange> natureMonthDateRanges = split2NatureMonthList(dateRange);
			//List<DateRange> monthDateRanges = split2MonthList(dateRange);
			//pickIncludeRange(natureMonthDateRanges, monthDateRanges, natureMonthList, remainingMonthList);
			List<DateRange> monthDateRangesByStart = split2MonthListByStartDay(dateRange);
			pickIncludeRange(monthDateRangesByStart, natureMonthList, remainingMonthList);
			result.put(DateRangeType.NATURE_MONTH, natureMonthList.size());
			//如果通过月份过滤后，所有日期都包含了，那直接返回
			if (remainingMonthList.isEmpty()) {
				return result;
			}
		}

		if (typeSet.contains(DateRangeType.NATURE_WEEK)) {
			List<DateRange> calcWeekRange = new ArrayList<>();
			//如果经过了月过滤，那么remainingMonthList大小必大于0，否则是没有经过月过滤
			//如果size=2,那么时前后都有，则需要看是否是相邻的两个时间段，如果是，则拼起来，如果不是，则保持两个
			if (remainingMonthList.size() == 2) {
				//如果首尾相连，则将其连接到一起
				DateRange dateRangeFirst = remainingMonthList.get(0);
				DateRange dateRangeLast = remainingMonthList.get(1);
				if (dateRangeFirst.getEndDate().plusDays(1).equals(dateRangeLast.getBeginDate())) {
					calcWeekRange.add(new DateRange(dateRangeFirst.getBeginDate(), dateRangeLast.getEndDate()));
				} else {
					calcWeekRange.add(dateRangeFirst);
					calcWeekRange.add(dateRangeLast);
				}
			} else if (remainingMonthList.size() == 1) {
				//前后只剩一个的时间段情况下，则直接取
				calcWeekRange.add(remainingMonthList.get(0));
			} else {
				calcWeekRange.add(dateRange);
			}

			for (DateRange range : calcWeekRange) {
				split2WeekList(weekList, remainingWeekList, range);
			}
			result.put(DateRangeType.NATURE_WEEK, weekList.size());
			//如果通过周过滤后，所有日期都包含了，那直接返回
			if (remainingWeekList.size() == 0) {
				return result;
			}
		}

		if (typeSet.contains(DateRangeType.NATURE_DAY)) {
			//如果只经过月过滤，没经过周过滤，那么此时remainingMonthList.size()>0
			//如果只经过周过滤，没经过月过滤，那么此时remainingWeekList.size()>0
			//如果即经过月过滤，又经过周过滤，那么此时remainingMonthList.size()>0并且remainingWeekList.size()>0，
			// 显然这里我们remainingWeekList的优先级更高，即在有remainingWeekList的情况下，不看remainingMonthList
			long days = 0L;
			if (remainingWeekList.size() > 0) {
				//从weekList取值
				for (DateRange range : remainingWeekList) {
					days = days + range.length();
				}
			} else if (remainingMonthList.size() > 0) {
				//从monthList取值
				for (DateRange range : remainingMonthList) {
					days = days + range.length();
				}
			} else {
				//直接从dataRange中取值
				days = dateRange.length();
			}
			result.put(DateRangeType.NATURE_DAY, (int) days);
		}

		return result;
	}

	/**
	 * 挑选出被包含的范围
	 */
	private static void pickIncludeRange(
			List<DateRange> dateRangeByStart,
			List<DateRange> natureList,
			List<DateRange> remainingList) {
		for (DateRange range : dateRangeByStart) {
			LocalDate start = range.getBeginDate();
			LocalDate startMonthFirstDay = range.getBeginDate().with(TemporalAdjusters.firstDayOfMonth());
			LocalDate end = range.getEndDate();
			LocalDate endMonthEndDay = range.getEndDate().with(TemporalAdjusters.lastDayOfMonth());
			if (!start.getMonth().equals(end.getMonth())){
				natureList.add(range);
			}else if (start.compareTo(startMonthFirstDay) == 0 && end.compareTo(endMonthEndDay) == 0 &&
					startMonthFirstDay.getMonth().equals(endMonthEndDay.getMonth())){
				natureList.add(range);
			}else {
				remainingList.add(range);
			}
		}
	}

	/**
	 * 挑选出被包含的范围
	 */
	private static void pickIncludeRange(
			List<DateRange> natureWeekDateRanges,
			List<DateRange> weekDateRanges,
			List<DateRange> natureWeekList,
			List<DateRange> remainingWeekList) {
		for (int i = 0; i < weekDateRanges.size(); i++) {
			DateRange dr = weekDateRanges.get(i);
			DateRange ndr = natureWeekDateRanges.get(i);
			if (dr.getBeginDate().compareTo(ndr.getBeginDate()) == 0 &&
					dr.getEndDate().compareTo(ndr.getEndDate()) == 0) {
				natureWeekList.add(ndr);
			} else {
				remainingWeekList.add(dr);
			}
		}
	}

	/**
	 * 类似：split2NatureMonthList
	 */
	private static void split2WeekList(List<DateRange> natureWeekList, List<DateRange> remainingWeekList, DateRange dateRange) {
		LocalDate beginDate = dateRange.getBeginDate();
		LocalDate endDate = dateRange.getEndDate();
		long days = endDate.toEpochDay() - beginDate.toEpochDay() + 1;
		if (days >= 7) {
			long remainDays = days % 7;
			LocalDate dayEndDate = beginDate.plusDays(remainDays - 1);
			if(0 != remainDays) {
				DateRange remain = new DateRange();
				remain.setBeginDate(beginDate);
				remain.setEndDate(dayEndDate);
				remainingWeekList.add(remain);
			}
			LocalDate weekStartDate = dayEndDate.plusDays(1);
			while (true) {
				LocalDate weekEndDate = weekStartDate.plusDays(6);
				if (weekEndDate.compareTo(endDate) > 0) {
					break;
				}
				DateRange weekRange = new DateRange();
				weekRange.setBeginDate(weekStartDate);
				weekRange.setEndDate(weekEndDate);
				natureWeekList.add(weekRange);
				weekStartDate = weekEndDate.plusDays(1);
			}
		} else {
			remainingWeekList.add(dateRange);
		}
	}

	/**
	 * 将原始日期，以开始日期为分割点，拆分成多个段
	 * 比如：2.10~4.10，则此处返回的是:
	 * 2.10~3.9
	 * 3.10~4.9
	 * 4.10~4.10
	 */
	private static List<DateRange> split2MonthListByStartDay(DateRange dateRange){
		List<DateRange> result = new ArrayList<>();
		LocalDate currentMonthBeginDate = dateRange.getBeginDate();
		LocalDate nextMonthFirstDay = getNextMonthFirstDay(currentMonthBeginDate);
		while (dateRange.getEndDate().compareTo(nextMonthFirstDay) >= 0) {
			LocalDate currentMonthEndDate = nextMonthFirstDay.minusDays(1);
			DateRange item = new DateRange(currentMonthBeginDate, currentMonthEndDate);
			result.add(item);
			currentMonthBeginDate = nextMonthFirstDay;
			nextMonthFirstDay = getNextMonthFirstDay(currentMonthBeginDate);
		}
		result.add(new DateRange(currentMonthBeginDate, dateRange.getEndDate()));
		return result;
	}

	private static LocalDate getNextMonthFirstDay(LocalDate currentMonthBeginDate){
		LocalDate nextMonthFirstDay = currentMonthBeginDate.plusMonths(1);
		if(currentMonthBeginDate.getDayOfMonth() != nextMonthFirstDay.getDayOfMonth()){
			nextMonthFirstDay = currentMonthBeginDate.plusMonths(1).plusDays(1);
		}
		return nextMonthFirstDay;
	}

	/**
	 * 将原始日期拆分成各个月的段
	 * 比如：2.10~4.10，则此处返回的是:
	 * 2.10~2.28
	 * 3.1~3.31
	 * 4.1~4.10
	 */
	private static List<DateRange> split2MonthList(DateRange dateRange) {
		List<DateRange> result = new ArrayList<>();
		LocalDate currentMonthBeginDate = dateRange.getBeginDate();
		LocalDate nextMonthFirstDay = dateRange.getBeginDate().plusMonths(1).withDayOfMonth(1);
		while (dateRange.getEndDate().compareTo(nextMonthFirstDay) >= 0) {
			LocalDate currentMonthEndDate = nextMonthFirstDay.minusDays(1);
			DateRange item = new DateRange(currentMonthBeginDate, currentMonthEndDate);
			result.add(item);
			currentMonthBeginDate = nextMonthFirstDay;
			nextMonthFirstDay = currentMonthBeginDate.plusMonths(1);
		}
		result.add(new DateRange(currentMonthBeginDate, dateRange.getEndDate()));
		return result;
	}

	/**
	 * 最大包含哪几个自然月
	 * 比如：2.10~4.10，则此处返回的是
	 * 2.1~2.28
	 * 3.1~3.31
	 * 4.1~4.30
	 */
	private static List<DateRange> split2NatureMonthList(DateRange dateRange) {
		List<DateRange> result = new ArrayList<>();
		LocalDate beginMonthDate = dateRange.getBeginDate().withDayOfMonth(1);
		LocalDate monthEndDate = dateRange.getEndDate().plusMonths(1).withDayOfMonth(1);
		LocalDate monthBeginDate = dateRange.getBeginDate().plusMonths(1).withDayOfMonth(1);
		while (monthBeginDate.compareTo(monthEndDate) <= 0) {
			DateRange item = new DateRange(beginMonthDate, monthBeginDate.minusDays(1));
			result.add(item);
			beginMonthDate = monthBeginDate;
			monthBeginDate = monthBeginDate.plusMonths(1);
		}
		return result;
	}

	public static void main(String[] args) {
		LocalDate beginDate = LocalDate.of(2024, 1, 30);
		LocalDate endDate = LocalDate.of(2024, 4, 30);
		Map<DateRangeType, Integer> calc = calc(beginDate, endDate, DateRangeType.NATURE_MONTH, DateRangeType.NATURE_DAY);
		System.out.println(calc.size());
	}

	/**
	 * 两个区间是否重合
	 */
	public static boolean isOverlap(LocalDate startDate1, LocalDate endDate1, LocalDate startDate2, LocalDate endDate2) {
		//不和自己比较
		if (endDate2.compareTo(endDate1) > 0) {
			return startDate2.compareTo(endDate1) <= 0;
		} else if (endDate2.compareTo(endDate1) == 0) {
			return true;
		} else {
			return startDate1.compareTo(endDate2) <= 0;
		}
	}

}
